﻿using System;
using System.Collections.Generic;
using System.Text;
using Pixysoft.Framework.Security.Tokens;
using System.IO;
using Pixysoft.Framework.Configurations;
using System.Diagnostics;

namespace Pixysoft.Framework.Security.Core
{
    class SSoTokenController
    {
        private const string default_filename_localcache = "pixysoft.framework.security.sso.tokencache.config";

        List<TokenCacheProfile> profilecache = null;//链接缓存 仅登录时建立

        Pixysoft.Cache.ObjectCache<TokenCacheProfile> tokencache = new Pixysoft.Cache.ObjectCache<TokenCacheProfile>(); //token 缓存


        private static volatile SSoTokenController instance;

        private static object syncRoot = new Object();

        public static SSoTokenController Instance
        {
            get
            {
                if (instance == null)
                {
                    lock (syncRoot)
                    {
                        if (instance == null)
                        {
                            instance = new SSoTokenController();
                        }
                    }
                }
                return instance;
            }
        }


        /// <summary>
        /// 如果来自本地缓存的profile token = 登录信息，否则就是远程即使生成的。
        /// </summary>
        /// <param name="username"></param>
        /// <param name="md5password"></param>
        /// <param name="timeoutMins"></param>
        /// <returns></returns>
        public string TokenLogin(string username, string md5password, int timeoutMins)
        {
            if (SettingHelper.Instance.SSoDisabled)
                return TokenSecurityManager.Instance.Login(username, md5password, timeoutMins);

            if (string.IsNullOrEmpty(username))
                return null;

            if (string.IsNullOrEmpty(md5password))
                return null;

            LoggerHelper.Debug("begin token login, sso disabled = {0}, sso token login, ssourl = {1}",
                SettingHelper.Instance.SSoDisabled, SettingHelper.Instance.SSoUrl);

            username = username.Trim().ToUpper();

            md5password = md5password.Trim();



            //获取登录信息

            string loginCacheKey = Pixysoft.Security.MD5.GetMD5(username + md5password);

            LoggerHelper.Debug("begin load profile from profile cache, username:{0}, logincachekey:{1}", username, loginCacheKey);

            TokenCacheProfile profile = LoadProfileFromProfileCache(username, md5password, loginCacheKey);

            if (profile == null)
            {
                //实时远程访问

                LoggerHelper.Debug("profile is null for username:{0}, create sso.", username);

                ISSoConnection connection = new SSoConnection(SettingHelper.Instance.SSoUrl);

                ISSoTokenLoginResponse response = null;

                try
                {
                    response = connection.TokenLogin(username, md5password, timeoutMins);

                    if (string.IsNullOrEmpty(response.Token))
                    {
                        LoggerHelper.Debug("sso return null for username:{0}", username);

                        return null;
                    }

                    profile = response.Profile;

                    LoggerHelper.Debug("load login profile from remote sso. username:{0}", username);
                }
                catch (Exception ex)
                {
                    LoggerHelper.Fatal(ex);

                    //尝试从本地缓存读取

                    profile = LoadProfileFromProfileCache(username, md5password, loginCacheKey);

                    if (profile == null)
                        return null;

                    LoggerHelper.Debug("load login profile from xml cache.");
                }
            }

            //add to cache

            string token = profile.Token;

            LoggerHelper.Debug("get token from profile, username:{0},token:{1}, logincachekey:{2}", username, token, loginCacheKey);

            profile.Username = username;

            profile.Md5password = md5password;

            profile.Token = loginCacheKey;//修改token 和登录信息挂钩 方便链接超时的时候可以验证复用

            if (!tokencache.Contains(token))
            {
                tokencache.Add(token, profile, TokenHelper.DateTimeNow.AddSeconds(TokenHelper.default_tokencache_Timeout));
            }
            else
            {
                tokencache.Extend(token, TokenHelper.DateTimeNow.AddSeconds(TokenHelper.default_tokencache_Timeout));
            }

            SaveProfileCache(profile);

            return token;
        }

        private TokenCacheProfile LoadProfileFromProfileCacheByToken(string token)
        {
            foreach (TokenCacheProfile profile in LoadProfileCache())
            {
                if (!string.Equals(profile.Token, token))
                    continue;

                return profile;
            }

            return null;
        }

        private TokenCacheProfile LoadProfileFromProfileCache(string username)
        {
            foreach (TokenCacheProfile profile in LoadProfileCache())
            {
                if (!string.Equals(profile.Username, username))
                    continue;

                return profile;
            }

            return null;
        }

        private TokenCacheProfile LoadProfileFromProfileCache(string username, string md5password, string token)
        {
            foreach (TokenCacheProfile profile in LoadProfileCache())
            {
                if (!string.Equals(profile.Username, username))
                    continue;

                if (!string.Equals(profile.Md5password, md5password))
                    continue;

                if (!string.Equals(profile.Token, token))
                    continue;

                return profile;
            }

            return null;
        }

        private TokenCacheProfile[] LoadProfileCache()
        {
            if (this.profilecache != null)
            {
                LoggerHelper.Debug("profile cache is in memory, return it.");

                return this.profilecache.ToArray();
            }

            this.profilecache = new List<TokenCacheProfile>();

            string filename = Pixysoft.StringFilter.RegularPath(AppDomain.CurrentDomain.BaseDirectory) + default_filename_localcache;

            if (!File.Exists(filename))
            {
                LoggerHelper.Debug("local cache does not existed on disk, just return profile cache. filename:{0}", filename);

                return this.profilecache.ToArray();
            }

            try
            {
                lock (syncRoot)
                {
                    IXmlCommand xmlcommand = XmlManager.CreateCommand(filename);
                    xmlcommand.Element = "TokenCacheProfile";
                    IXmlTree tree = xmlcommand.ExecuteReader();

                    foreach (IXmlNode node in tree.Nodes)
                    {
                        TokenCacheProfile profile = new TokenCacheProfile(node.Node["Token"].Text, node.Node["Usercode"].Text);
                        profile.IsSuperUser = bool.Parse(node.Node["IsSuperUser"].Text);
                        profile.Username = node.Node["Username"].Text;
                        profile.Md5password = node.Node["Md5password"].Text;
                        foreach (IXmlNode accessnode in node.Node["AccessList"].Nodes["string"])
                        {
                            profile.AccessList.Add(accessnode.Text);
                        }
                        this.profilecache.Add(profile);
                    }
                }
            }
            catch (Exception ex)
            {
                LoggerHelper.Error("read local profile cache fail.");

                LoggerHelper.Fatal(ex);
            }

            LoggerHelper.Debug("profile cache loaded from local xml cache, count:{0}", this.profilecache.Count);

            return this.profilecache.ToArray();
        }

        private void SaveProfileCache(TokenCacheProfile profile)
        {
            LoggerHelper.Debug("begin save profile to cache. profile.username:{0}", profile.Username);

            //如果用户信息没有修改 则不保存

            if (LoadProfileFromProfileCache(profile.Username, profile.Md5password, profile.Token) != null)
            {
                LoggerHelper.Debug("load profile from cache (username/password/token) return true, nothing to change, return it.");

                return;
            }

            TokenCacheProfile oldprofile = LoadProfileFromProfileCache(profile.Username);

            lock (syncRoot)
            {
                if (oldprofile != null)
                {
                    this.profilecache.Remove(oldprofile);
                }

                this.profilecache.Add(profile);
            }

            LoggerHelper.Debug("replace profile to local xml cache, username:{0}", profile.Username);

            try
            {
                string filename = Pixysoft.StringFilter.RegularPath(AppDomain.CurrentDomain.BaseDirectory) + default_filename_localcache;

                Pixysoft.IO.XmlExporter.Instance.XmlExport(filename, this.profilecache);

                LoggerHelper.Debug("save xml cache to disk. username:{0}, count:{1}", profile.Username, this.profilecache.Count);

            }
            catch (Exception ex)
            {
                LoggerHelper.Fatal(ex);
            }
        }



        public void TokenLogout(string token)
        {
            if (SettingHelper.Instance.SSoDisabled)
            {
                TokenSecurityManager.Instance.Logout(token);
                return;
            }

            //ISSoConnection connection = new SSoConnection(SettingHelper.Instance.SSoUrl);

            //try
            //{
            //    connection.TokenLogout(token);
            //}
            //catch (Exception ex)
            //{
            //    LoggerHelper.Fatal(ex);
            //}

            if (tokencache.Contains(token))
                tokencache.Remove(token);
        }

        public bool TokenValidate(string token)
        {
            if (SettingHelper.Instance.SSoDisabled)
                return TokenSecurityManager.Instance.TokenValidate(token);

            if (string.IsNullOrEmpty(token))
                return false;

            //内存缓存读取

            if (tokencache.Contains(token))
            {
                LoggerHelper.Debug("token verify from cache. token = {0}", token);

                return true;
            }

            //对象持久读取

            TokenCacheProfile profile = LoadProfileFromProfileCacheByToken(token);

            if (profile != null)
            {
                LoggerHelper.Debug("token verify from xml profile cache. token = {0}", token);

                if (!tokencache.Contains(token))
                    tokencache.Add(token, profile, TokenHelper.DateTimeNow.AddSeconds(TokenHelper.default_tokencache_Timeout));

                return true;
            }


            //远程链接

            LoggerHelper.Debug("begin token verify from sso connection, token:{0}", token);

            ISSoConnection connection = new SSoConnection(SettingHelper.Instance.SSoUrl);

            ISSoTokenLoginResponse response = null;

            try
            {
                response = connection.TokenValidate(token);
            }
            catch (Exception ex)
            {
                LoggerHelper.Fatal(ex);

                return false;
            }

            if (!response.Result)
                return false;

            //add to cache

            profile = response.Profile;

            if (!tokencache.Contains(token))
                tokencache.Add(token, profile, TokenHelper.DateTimeNow.AddSeconds(TokenHelper.default_tokencache_Timeout));

            return true;
        }

        public bool TokenValidate(string token, string accessname)
        {
            if (SettingHelper.Instance.SSoDisabled)
                return TokenSecurityManager.Instance.TokenValidate(token, accessname);

            if (string.IsNullOrEmpty(token))
                return false;

            if (string.IsNullOrEmpty(accessname))
                return false;

            accessname = accessname.Trim().ToUpper();


            TokenCacheProfile profile = null;

            //内存token读取

            if (tokencache.Contains(token))
            {
                LoggerHelper.Debug("token verify from cache. token = {0}", token);

                profile = tokencache.Get(token);
            }

            //对象持久读取

            if (profile == null)
            {
                LoggerHelper.Debug("token verify from xml profile cache. token = {0}", token);

                profile = LoadProfileFromProfileCacheByToken(token);
            }

            if (profile != null)
            {
                if (!tokencache.Contains(token))
                    tokencache.Add(token, profile, TokenHelper.DateTimeNow.AddSeconds(TokenHelper.default_tokencache_Timeout));

                if (profile.IsSuperUser)
                    return true;

                return profile.AccessList.Contains(accessname);
            }


            //实际链接读取

            ISSoConnection connection = new SSoConnection(SettingHelper.Instance.SSoUrl);

            ISSoTokenLoginResponse response = null;

            try
            {
                response = connection.TokenValidate(token);
            }
            catch (Exception ex)
            {
                LoggerHelper.Fatal(ex);
                return false;
            }

            if (!response.Result)
                return false;

            //add to cache

            TokenCacheProfile newprofile = response.Profile;

            if (!tokencache.Contains(token))
                tokencache.Add(token, newprofile, TokenHelper.DateTimeNow.AddSeconds(TokenHelper.default_tokencache_Timeout));

            if (newprofile.IsSuperUser)
                return true;

            return newprofile.AccessList.Contains(accessname);
        }

        public bool TokenExtend(string token)
        {
            if (SettingHelper.Instance.SSoDisabled)
                return TokenSecurityManager.Instance.TokenExtend(token);

            if (string.IsNullOrEmpty(token))
                return false;

            if (tokencache.Contains(token))
            {
                LoggerHelper.Debug("token extend from cache. token = {0}", token);

                tokencache.Extend(token, TokenHelper.DateTimeNow.AddSeconds(TokenHelper.default_tokencache_Timeout));

                return true;
            }


            //对象持久读取

            TokenCacheProfile profile = LoadProfileFromProfileCacheByToken(token);

            if (profile != null)
            {
                if (!tokencache.Contains(token))
                    tokencache.Add(token, profile, TokenHelper.DateTimeNow.AddSeconds(TokenHelper.default_tokencache_Timeout));

                return true;
            }



            ISSoConnection connection = new SSoConnection(SettingHelper.Instance.SSoUrl);

            ISSoTokenLoginResponse response = null;

            try
            {
                response = connection.TokenExtend(token);
            }
            catch (Exception ex)
            {
                LoggerHelper.Fatal(ex);
                return false;
            }

            if (!response.Result)
                return false;

            profile = response.Profile;

            if (!tokencache.Contains(token))
                tokencache.Add(token, profile, TokenHelper.DateTimeNow.AddSeconds(TokenHelper.default_tokencache_Timeout));

            return true;
        }
    }
}
